import { Line, splitLine } from "./Line.js";
import { setElement as updateElement, type INode } from "./types.js";

/** 空格 */
const SPACE = " ".charCodeAt(0);
/** 冒号 */
const COLON = ":".charCodeAt(0);
/** 句号 */
const PERIOD = ".".charCodeAt(0);
/** @ */
const AT = "@".charCodeAt(0);
/** $ */
const DOLLAR = "$".charCodeAt(0);

/** 占位符字符串 */
const PLACEHOLDER = "$";

/**
 * 将对象转换为占位符字符串，并存储在元素数组中
 * @param elements - 存储对象引用的数组
 * @param item - 需要转换为占位符的对象
 * @returns 占位符字符串
 */
function toString(elements: unknown[], item: object): string {
    elements.push(item);
    return PLACEHOLDER;
}

/**
 * 将模板字符串与参数组合，将对象转换为占位符
 * @param template - 模板字符串数组
 * @param args - 要插入的参数
 * @param placeholders - 存储对象引用的数组
 * @returns 组合后的字符串
 */
function joinString(template: TemplateStringsArray, args: any[], placeholders: unknown[]): string {
    const max = template.length - 1;
    let str = "";
    let item: any;
    for (let i = 0; i < max; i++) {
        str += template[i];
        item = args[i];
        switch (typeof item) {
            case "string":
                str += item;
                break;
            case "object":
                str += toString(placeholders, item);
                break;
            default:
                throw new Error(`Unsupported type: ${typeof item}`);
        }
    }
    str += template[template.length - 1];
    return str;
}

/**
 * 根据解析的行创建DOM元素
 * @param line - 包含标签信息的解析行
 * @param placeholders - 用于替换占位符的对象数组
 * @returns 创建的DOM节点
 * @throws 如果标签为空或占位符替换无效时抛出错误
 */
function createElement(line: Line, placeholders: unknown[]): INode {
    const tag = line.getTagName();
    if (tag === PLACEHOLDER) {
        const item = placeholders.shift();
        if (item instanceof HTMLElement || item instanceof ShadowRoot) {
            return item;
        }
        throw new Error(`Invalid iTag, item: ${typeof item}, ${item?.constructor?.name}`);
    }
    if (tag === "") {
        throw new Error(`tag is empty, line: ${line.toString()}`);
    }
    return document.createElement(tag);
}

/**
 * 设置元素的属性、类名、文本内容等
 * @param element - 要设置的DOM元素
 * @param line - 包含设置信息的解析行
 * @param placeholders - 用于替换占位符的对象数组
 */
function setElement(element: INode, line: Line, placeholders: any[]): void {
    for (const flag of line.charCodes()) {
        switch (flag) {
            case SPACE: // 空格
                break;
            case COLON: // 冒号
                element.textContent = line.getRest().trim();
                break;
            case PERIOD: // 句号
                if (!(element instanceof HTMLElement)) {
                    throw new Error("element is not HTMLElement");
                }
                element.classList.add(line.getClassName());
                break;
            case AT: // @
                if (element instanceof HTMLElement) {
                    element.setAttribute(...line.getAttribute());
                } else {
                    throw new Error("element is not HTMLElement");
                }
                break;
            case DOLLAR: {
                // $
                const item = placeholders.shift();
                if (updateElement(element, item)) {
                } else {
                    console.warn("Invalid element setter: %o", item);
                }
                break;
            }

            default:
                throw new Error(`Invalid flag: ${String.fromCharCode(flag)}`);
        }
    }
}

/**
 * 表示带有缩进信息的元素
 * 用于构建DOM树时跟踪元素层级关系
 */
interface IndentElement {
    /** 元素的缩进级别 */
    readonly indent: number;
    /** DOM元素 */
    readonly element: INode;
}

/**
 * 根据缩进级别查找父元素
 * @param elements - 带有缩进信息的元素数组
 * @param indent - 当前元素的缩进级别
 * @returns 找到的父元素，如果没有找到则返回undefined
 */
function findParent(elements: IndentElement[], indent: number): IndentElement | undefined {
    for (let i = elements.length - 1; i >= 0; i--) {
        if (elements[i]!.indent < indent) {
            return elements[i];
        }
    }
    return undefined;
}

/**
 * 使用模板字符串创建DOM树
 *
 * 语法规则:
 * - 缩进: 建立父子关系
 * - 标签名: 创建指定类型的元素
 * - .: 添加CSS类名
 * - @: 设置属性 (格式: @属性名=属性值)
 * - :: 设置文本内容
 * - $: 对象占位符，用于插入或操作DOM元素
 *
 * @param template - 模板字符串
 * @param args - 插入模板的值
 * @returns 根DOM节点数组
 *
 * @example
 * const [div] = iTag`
 *   div.container
 *     h1: 标题
 *     p@data-info=详情: 这是一段文字
 *     button.primary: ${clickHandler}
 * `;
 */
export function itag(template: TemplateStringsArray, ...args: any[]): INode[] {
    if (template.length === 0) {
        throw new Error("template is empty");
    }
    const placeholders: unknown[] = [];
    const str = joinString(template, args, placeholders);
    const results: INode[] = [];
    const indents: IndentElement[] = [];
    for (const line of splitLine(str)) {
        if (line.isEmptyOrSpace()) {
            continue;
        }
        const indent = line.getIndent();
        const element = createElement(line, placeholders);
        setElement(element, line, placeholders);
        const parent = findParent(indents, indent);
        if (parent) {
            parent.element.appendChild(element);
        } else {
            results.push(element);
        }
        indents.push({ indent, element });
    }
    return results;
}
